package pay

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"

	"gitee.com/githubdog/goci/libraries/mylog"
	"gitee.com/githubdog/goci/tools/helper"
	"gitee.com/githubdog/goci/tools/myerr"
)

// 文档地址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

// WeiXinPay 微信支付
type WeiXinPay struct {
	Appid          string `xml:"appid"`                 // 应用ID
	MchID          string `xml:"mch_id"`                // 商户号
	DeviceInfo     string `xml:"device_info,omitempty"` // 设备号
	NonceStr       string `xml:"nonce_str"`             // 随机字符串
	Sign           string `xml:"sign"`                  // 签名
	SignType       string `xml:"sign_type,omitempty"`   //签名类型
	Body           string `xml:"body"`                  // 商品描述
	Detail         string `xml:"detail,omitempty"`      // 商品详情
	Attach         string `xml:"attach,omitempty"`      // 附加数据
	OutTradeNo     string `xml:"out_trade_no"`          // 商户订单号
	FeeType        string `xml:"fee_type,omitempty"`    // 货币类型
	TotalFee       string `xml:"total_fee"`             // 总金额
	SpbillCreateIP string `xml:"spbill_create_ip"`      // 终端IP
	TimeStart      string `xml:"time_start,omitempty"`  // 交易起始时间
	TimeExpire     string `xml:"time_expire,omitempty"` // 交易结束时间
	GoodsTag       string `xml:"goods_tag,omitempty"`   // 订单优惠标记
	NotifyURL      string `xml:"notify_url"`            // 通知地址
	TradeType      string `xml:"trade_type"`            // 交易类型
	LimitPay       string `xml:"limit_pay,omitempty"`   // 指定支付方式
	Openid         string `xml:"openid,omitempty"`      // trade_type=JSAPI时, 此参数必传
	Receipt        string `xml:"receipt,omitempty"`     // 开发票入口开放标识
	SceneInfo      string `xml:"scene_info,omitempty"`  // 场景信息 json
}

// WxPayResponse 微信支付返回内容
type WxPayResponse struct {
	ReturnCode string `xml:"return_code"`            // 返回状态码 SUCCESS/FAIL
	ReturnMsg  string `xml:"return_msg,omitempty"`   // 返回信息
	Appid      string `xml:"appid"`                  // 应用APPID
	MchID      string `xml:"mch_id"`                 // 商户号
	DeviceInfo string `xml:"device_info,omitempty"`  // 设备号
	NonceStr   string `xml:"nonce_str"`              // 随机字符串
	Sign       string `xml:"sign"`                   // 签名
	ResultCode string `xml:"result_code"`            // 业务结果 SUCCESS/FAIL
	ErrCode    string `xml:"err_code,omitempty"`     // 错误代码
	ErrCodeDes string `xml:"err_code_des,omitempty"` // 错误代码描述
	TradeType  string `xml:"trade_type"`             // 交易类型
	PrepayID   string `xml:"prepay_id"`              // 预支付交易会话标识
}

// WxPayCbResponse 微信支付回调参数
type WxPayCbResponse struct {
	ReturnCode         string `xml:"return_code"`
	ReturnMsg          string `xml:"return_msg,omitempty"`
	Appid              string `xml:"appid"`
	MchID              string `xml:"mch_id"`                         // 商户号
	DeviceInfo         string `xml:"device_info,omitempty"`          // 设备号
	NonceStr           string `xml:"nonce_str"`                      // 随机字符串
	Sign               string `xml:"sign"`                           // 签名
	SignType           string `xml:"sign_type,omitempty"`            // 签名类型
	ResultCode         string `xml:"result_code"`                    // 业务结果
	ErrCode            string `xml:"err_code,omitempty"`             // 错误代码
	ErrCodeDes         string `xml:"err_code_des,omitempty"`         // 错误代码描述
	Openid             string `xml:"openid"`                         // 用户标识
	IsSubscribe        string `xml:"is_subscribe"`                   // 是否关注公众账号
	TradeType          string `xml:"trade_type"`                     // 交易类型
	BankType           string `xml:"bank_type"`                      // 付款银行
	TotalFee           string `xml:"total_fee"`                      // 订单金额，单位为分 int
	SettlementTotalFee string `xml:"settlement_total_fee,omitempty"` // 应结订单金额 int
	FeeType            string `xml:"fee_type,omitempty"`             // 货币种类
	CashFee            string `xml:"cash_fee"`                       // 现金支付金额 int
	CashFeeType        string `xml:"cash_fee_type,omitempty"`        // 现金支付货币类型
	CouponFee          string `xml:"coupon_fee,omitempty"`           // 总代金券金额 int
	TransactionID      string `xml:"transaction_id"`                 // 微信支付订单号
	OutTradeNo         string `xml:"out_trade_no"`                   // 商户订单号
	Attach             string `xml:"attach,omitempty"`               // 商家数据包
	TimeEnd            string `xml:"time_end"`                       // 支付完成时间
	// TODO
	CouponCount string `xml:"coupon_count,omitempty"`  // 代金券使用数量
	CouponType0 string `xml:"coupon_type_0,omitempty"` // 代金券类型
	CouponType1 string `xml:"coupon_type_1,omitempty"` // 代金券类型
	CouponType2 string `xml:"coupon_type_2,omitempty"` // 代金券类型
	CouponType3 string `xml:"coupon_type_3,omitempty"` // 代金券类型
	CouponType4 string `xml:"coupon_type_4,omitempty"` // 代金券类型
	CouponType5 string `xml:"coupon_type_5,omitempty"` // 代金券类型
	CouponType6 string `xml:"coupon_type_6,omitempty"` // 代金券类型
	CouponType7 string `xml:"coupon_type_7,omitempty"` // 代金券类型
	CouponType8 string `xml:"coupon_type_8,omitempty"` // 代金券类型
	CouponType9 string `xml:"coupon_type_9,omitempty"` // 代金券类型
	CouponID0   string `xml:"coupon_id_0,omitempty"`   // 代金券ID
	CouponID1   string `xml:"coupon_id_1,omitempty"`   // 代金券ID
	CouponID2   string `xml:"coupon_id_2,omitempty"`   // 代金券ID
	CouponID3   string `xml:"coupon_id_3,omitempty"`   // 代金券ID
	CouponID4   string `xml:"coupon_id_4,omitempty"`   // 代金券ID
	CouponID5   string `xml:"coupon_id_5,omitempty"`   // 代金券ID
	CouponID6   string `xml:"coupon_id_6,omitempty"`   // 代金券ID
	CouponID7   string `xml:"coupon_id_7,omitempty"`   // 代金券ID
	CouponID8   string `xml:"coupon_id_8,omitempty"`   // 代金券ID
	CouponID9   string `xml:"coupon_id_9,omitempty"`   // 代金券ID
	CouponFee0  string `xml:"coupon_fee_0,omitempty"`  // 单个代金券支付金额
	CouponFee1  string `xml:"coupon_fee_1,omitempty"`  // 单个代金券支付金额
	CouponFee2  string `xml:"coupon_fee_2,omitempty"`  // 单个代金券支付金额
	CouponFee3  string `xml:"coupon_fee_3,omitempty"`  // 单个代金券支付金额
	CouponFee4  string `xml:"coupon_fee_4,omitempty"`  // 单个代金券支付金额
	CouponFee5  string `xml:"coupon_fee_5,omitempty"`  // 单个代金券支付金额
	CouponFee6  string `xml:"coupon_fee_6,omitempty"`  // 单个代金券支付金额
	CouponFee7  string `xml:"coupon_fee_7,omitempty"`  // 单个代金券支付金额
	CouponFee8  string `xml:"coupon_fee_8,omitempty"`  // 单个代金券支付金额
	CouponFee9  string `xml:"coupon_fee_9,omitempty"`  // 单个代金券支付金额
}

// WxPayAppRequest app发起微信支付请求的参数
type WxPayAppRequest struct {
	Appid     string `json:"appid"`              // 应用ID
	Partnerid string `json:"partnerid"`          // 商户号
	Prepayid  string `json:"prepayid"`           // 预支付交易会话ID
	Package   string `json:"package,omitempty"`  // 扩展字段 暂填写固定值Sign=WXPay
	Packagex  string `json:"packagex,omitempty"` // package可能存在解析冲突(据android开发人员反映)
	Noncestr  string `json:"noncestr"`           // 随机字符串
	Timestamp string `json:"timestamp"`          // 时间戳
	Sign      string `json:"sign"`               // 签名
}

// WxPayJsRequest js发起微信支付请求的参数
type WxPayJsRequest struct {
	Appid     string `json:"appId"`     // 应用ID
	Timestamp string `json:"timeStamp"` // 时间戳
	Noncestr  string `json:"nonceStr"`  // 随机字符串
	Package   string `json:"package"`   // 统一下单接口返回的prepay_id参数值，提交格式如：prepay_id=***
	SignType  string `json:"signType"`  // 签名类型
	PaySign   string `json:"paySign"`   // 签名
}

// RemoveClashField 去除冲突字段
func (wxr *WxPayAppRequest) RemoveClashField() {
	wxr.Packagex = wxr.Package
	wxr.Package = ""
}

var (
	wxPayURL            = "https://api.mch.weixin.qq.com/pay/unifiedorder"
	appMap              = map[string]string{} // appid => appsecret
	mchMap              = map[string]string{} // mchid => key
	LogWxChannel string = "weixin"
	LogWxFile    string
)

func AppendWxAppMap(appid, secret string) {
	appMap[appid] = secret
}

func AppendWxMchMap(mchid, key string) {
	mchMap[mchid] = key
}

// InitWeiXinPay 初始化微信支付
func InitWeiXinPay(appid, mchid string) *WeiXinPay {
	return &WeiXinPay{
		Appid:    appid,
		MchID:    mchid,
		NonceStr: helper.GenerateUUID(),
		SignType: "HMAC-SHA256",
	}
}

// SetFloatMoney 设置元的金额
func (wx *WeiXinPay) SetFloatMoney(money float64) {
	wx.TotalFee = strconv.Itoa(helper.Round(money * 100))
}

// GetWxPaySign 微信支付签名
func GetWxPaySign(signTempStr, mchid string) string {
	signTempStr += "&key=" + mchMap[mchid]
	return helper.GenerateSignByHmacSha256([]byte(signTempStr), []byte(mchMap[mchid]))
}

// StartPayRequest 开始支付请求
func (wx *WeiXinPay) StartPayRequest() (WxPayResponse, myerr.ErrMsg) {
	signContent := helper.GetSignContent(helper.FormatToMap(wx, "xml"), "")
	if wx.Sign == "" {
		wx.Sign = GetWxPaySign(signContent, wx.MchID)
	}
	res := WxPayResponse{}
	ret := myerr.ErrMsg{}

	if btys, err := xml.Marshal(*wx); err != nil {
		ret.Err = err
	} else {
		if request, err := http.NewRequest(
			http.MethodPost,
			wxPayURL,
			bytes.NewBuffer(btys)); err != nil {
			ret.Err = err
		} else if resp, err := http.DefaultClient.Do(request); err != nil {
			ret.Err = err
		} else {
			defer resp.Body.Close()
			if r, err := ioutil.ReadAll(resp.Body); err != nil {
				ret.Err = err
			} else if err = xml.Unmarshal(r, &res); err != nil {
				ret.Err = err
			} else {
				signContent2 := helper.GetSignContent(helper.FormatToMap(&res, "xml"), "")
				sign := GetWxPaySign(signContent2, wx.MchID)
				if sign != res.Sign {
					ret.Msg = "验签失败"
					mylog.WriteLog(LogWxChannel, LogWxFile, string(r), "\nRequest sign string:", signContent, "\nResponse sign string:", signContent2)
				} else if res.ReturnCode != "SUCCESS" {
					ret.Msg = res.ReturnMsg
				} else if res.ResultCode != "SUCCESS" {
					ret.Msg = fmt.Sprintf("[%s]%s", res.ErrCode, res.ErrCodeDes)
					mylog.WriteLog(LogWxChannel, LogWxFile, string(r), "\nRequest data string:", signContent)
				} else {
					ret.Ret = true
					ret.Msg = "success"
				}
			}
		}
	}
	return res, ret
}

// GetWeixinAPPPayParams 获取微信APP支付的参数
/*
<xml><appid><![CDATA[wx7c0a0b25c5d97194]]></appid>
<bank_type><![CDATA[OTHERS]]></bank_type>
<cash_fee><![CDATA[60]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1624412099]]></mch_id>
<nonce_str><![CDATA[0349eefeaa9a4958b6ad2df2847fefde]]></nonce_str>
<openid><![CDATA[owReZ6GwQBG4qu6ygCTp1TkAtCc4]]></openid>
<out_trade_no><![CDATA[acd2465f4c814b1c9fc34f1b8767e66f]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[B2AF21F2E336A4B0A8B68EC993B49AACB0A31D70F132EF4E4F2C6846C28DBF28]]></sign>
<time_end><![CDATA[20220918104310]]></time_end>
<total_fee>60</total_fee>
<trade_type><![CDATA[APP]]></trade_type>
<transaction_id><![CDATA[4200001606202209185239303909]]></transaction_id>
</xml>
*/
func (wx *WeiXinPay) GetWeixinAPPPayParams() (WxPayAppRequest, myerr.ErrMsg) {
	data := WxPayAppRequest{}
	ret := myerr.ErrMsg{}
	wx.TradeType = "APP"
	res, r := wx.StartPayRequest()
	ret = r
	if r.Ret {
		data.Appid = res.Appid
		data.Partnerid = res.MchID
		data.Noncestr = res.NonceStr
		data.Package = "Sign=WXPay"
		data.Prepayid = res.PrepayID
		data.Timestamp = strconv.FormatInt(time.Now().Unix(), 10)
		signContent := helper.GetSignContent(helper.FormatToMap(&data, "json"), "sign")
		data.Sign = GetWxPaySign(signContent, wx.MchID)
	}
	return data, ret
}

// GetWeixinJsPayParams 获取微信js支付的参数
func (wx *WeiXinPay) GetWeixinJsPayParams(openid string) (WxPayJsRequest, myerr.ErrMsg) {
	data := WxPayJsRequest{}
	ret := myerr.ErrMsg{}
	wx.TradeType = "JSAPI"
	wx.Openid = openid
	res, r := wx.StartPayRequest()
	ret = r
	if r.Ret {
		data.Appid = res.Appid
		data.Timestamp = strconv.FormatInt(time.Now().Unix(), 10)
		data.Noncestr = res.NonceStr
		data.Package = "prepay_id=" + res.PrepayID
		data.SignType = "HMAC-SHA256"
		signContent := helper.GetSignContent(helper.FormatToMap(&data, "json"), "paySign")
		data.PaySign = GetWxPaySign(signContent, wx.MchID)
	}
	return data, ret
}

// CheckPayCallback 检查支付回调
// logType 日志类型: 0 不记, 1 所有, 2 失败和报错, 3 仅失败, 4 仅报错, 5 仅成功
// 返回字段说明: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
func CheckPayCallback(raw []byte, logType uint8) (WxPayCbResponse, myerr.ErrMsg) {
	data := WxPayCbResponse{}
	ret := myerr.ErrMsg{}
	if err := xml.Unmarshal(raw, &data); err != nil {
		ret.Err = err
	} else {
		signContent := helper.GetSignContent(helper.FormatToMap(&data, "xml"), "")
		if data.Sign != GetWxPaySign(signContent, data.MchID) {
			ret.Msg = "验签失败"
		} else if data.ReturnCode != "SUCCESS" {
			ret.Msg = data.ReturnMsg
		} else if data.ResultCode != "SUCCESS" {
			ret.Msg = fmt.Sprintf("[%s]%s", data.ErrCode, data.ErrCodeDes)
		} else {
			ret.Ret = true
			ret.Msg = "success"
		}
	}
	if logType > 0 {
		logParams := []interface{}{
			fmt.Sprintf("VerifySignByWeixinCbRaw 回调内容:[%s]", string(raw)),
		}
		if ret.Err != nil && (logType == 1 || logType == 2 || logType == 4) {
			logParams = append(logParams, "\n出错:", ret.Err)
		}
		if !ret.Ret && (logType == 1 || logType == 2 || logType == 3) {
			logParams = append(logParams, "\n验签失败:", ret.Msg)
		}
		if ret.Ret && (logType == 1 || logType == 5) {
			logParams = append(logParams, "\n验签成功!")
		}
		mylog.WriteLog(LogWxChannel, LogWxFile, logParams...)
	}
	return data, ret
}
